<template>
  <div ref="color-palatte" name="colorPalatte" class="colorPalatte" v-show="showPalatte">
    <div name="ringSelector" class="ringSelector" @mousedown="ringMousedown" @touchstart="ringMousedown"></div>
    <div name="palatteSelector" class="plaltteSelector" @mousedown="palatteMousedown" @touchstart="palatteMousedown"></div>
    <div name="sliderSelector" class="sliderSelector" @mousedown="sliderMousedown" @touchstart.stop="sliderMousedown"></div>
    <canvas width="201px" height="240px" class="canvas" @mousedown="handlePalatteMouseDown" @touchstart.stop="handlePalatteMouseDown"></canvas>
    <input name="result" type="text" class="result" v-model="colorResult" @change="handleResultInput()" @keyup.enter="handleResultInput()"/>
    <button name="confirm" type="button" class="confirmBtn" @click="handleConfirm()">确认</button>
    <div name="close" type="button" class="closeBtn" @click="close()">&times;</div>
  </div>
</template>

<script>
/* eslint one-var: 0, no-multi-spaces: 0,max-len: 0 */
let That = null;

/**
 * 函数去抖
 */
function debounce(func, delay) {
  let timer;

  return (...args) => {
    if (timer) {
      clearTimeout(timer);
    }
    timer = setTimeout(() => {
      func.apply(this, args);
    }, delay);
  };
}

export default {
  name: 'ColorPalatte',
  props: {
    show: Boolean,
    defaultColor: String
  },
  data() {
    return {
      contanerObj: null,        // 调色板容器
      palatteObj: null,         // 画布对象
      ringSelectorObj: null,    // 色环选择器对象
      palatteSelectorObj: null, // 色块选择器对象
      sliderSelectorObj: null,  // 透明度选择器对象
      colorResult: '#FF0000',   // 色彩文本
      colorConfirmObj: null,    // 确认按钮对象
      closeObj: null,           // 关闭按钮对象
      palatteCTX: null,         // 画布上下文
      positionDict: {},         // 当前选中的三个位置属性 {ringPos:{x,y}, palattePos:{x,y}, sliderPos:{x,y}}
      ringDownFlag: false,      // 色环选择器按下标识
      palatteDownFlag: false,   // 色块选择器按下标识
      sliderDownFlag: false,    // 透明度选择器按下标识
      gobalOpts: {},
      opt: {                    // 画布属性
        width: 201,                         // 画布宽
        height: 240,                        // 画布高
        center: {x: 100, y: 100},           // 色环圆心坐标
        radius: {outter: 99, inner: 85},    // 色环外径、内径
        wearProof: 0.5,                     // 色环细腻度(>0,越小越细腻)
        DefaultColor: '#FF0000',            // 默认显示颜色
        palattePos: {x: 50, y: 50},         // 方形灰度选择块左顶点坐标
        palatteSize: {w: 100, h: 100},      // 方形灰度选择块宽高
        sliderPos: {x: 60, y: 210},         // 透明度滑动条左顶点坐标
        sliderSize: {w: 140, h: 20},        // 透明度滑动条宽高
        previewPos: {x: 1, y: 210},         // 预览左顶点坐标
        previewSize: {w: 50, h: 20},        // 预览宽高
        ringSelectorSize: 8,                // 色环选择器宽高
        palatteSelectorSize: 4,             // 色环选择器宽高
        sliderSelectorSize: {w: 4, h: 18}   // 透明度选择器宽高
      },
      showPalatte: true
    };
  },
  watch: {
    show: {
      handler(val) {
        this.showPalatte = val;
      },
      immediate: true
    }
  },
  created() {
    if (![null, undefined].includes(this.show)) {
      this.showPalatte = this.show;
    }
    That = this;
  },
  mounted() {
    this.init();
  },
  methods: {
    init() {
      this.contanerObj = this.$refs['color-palatte']; // 调色板容器
      this.palatteObj = this.contanerObj.getElementsByClassName('canvas')[0]; // 画布对象
      this.ringSelectorObj = this.contanerObj.getElementsByClassName('ringSelector')[0]; // 色环选择器对象
      this.palatteSelectorObj = this.contanerObj.getElementsByClassName('plaltteSelector')[0]; // 色块选择器对象
      this.sliderSelectorObj = this.contanerObj.getElementsByClassName('sliderSelector')[0]; // 透明度选择器对象
      this.colorConfirmObj = this.contanerObj.getElementsByClassName('confirmBtn')[0]; // 确认按钮对象
      this.closeObj = this.contanerObj.getElementsByClassName('closeBtn')[0]; // 关闭按钮对象

      this.palatteCTX = this.palatteObj.getContext('2d'); // 画布上下文
      if (this.defaultColor && ((/^#[\dA-F]{3}$/.test(this.defaultColor) || /^#[\dA-F]{6}$/.test(this.defaultColor) || /^#[\dA-F]{8}$/.test(this.defaultColor)))) {
        this.opt.DefaultColor = this.defaultColor;
      }
      this.gobalOpts.color = this.opt.DefaultColor;
      this.colorResult = this.opt.DefaultColor;

      // 防止选中
      this.contanerObj.onselectstart = () => { return false; };
      this.contanerObj.oncontextmenu = () => { return false; };

      // 产生色环
      this.colorRing(this.palatteCTX, this.opt.center.x, this.opt.center.y, this.opt.radius.outter, this.opt.radius.inner, this.opt.wearProof);
      // 产生中间方形灰度选择块
      this.colorPalatte(this.palatteCTX, this.opt.palattePos.x, this.opt.palattePos.y, this.opt.palatteSize.w, this.opt.palatteSize.h, this.opt.DefaultColor);
      // 产生透明度滑动条
      this.colorSlider(this.palatteCTX, this.opt.sliderPos.x, this.opt.sliderPos.y, this.opt.sliderSize.w, this.opt.sliderSize.h, this.opt.DefaultColor);
      // 产生预览
      this.colorPreview(this.palatteCTX, this.opt.previewPos.x, this.opt.previewPos.y, this.opt.previewSize.w, this.opt.previewSize.h, this.opt.DefaultColor, this.opt.DefaultColor);
      // 定义初始化后的三个位置参数
      this.positionDict = {
        ringPos: {x: (this.opt.center.x + (this.opt.radius.outter + this.opt.radius.inner) / 2), y: this.opt.center.y},
        palattePos: {x: this.opt.palattePos.x + this.opt.palatteSize.w, y: 0},
        sliderPos: {x: this.opt.sliderPos.x + this.opt.sliderSize.w - this.opt.sliderSelectorSize.w, y: 0}
      };
      // 根据 DefaultColor 重置颜色
      this.resetSelector(this.palatteCTX, this.ringSelectorObj, this.palatteSelectorObj, this.sliderSelectorObj, this.opt.DefaultColor);
      this.contanerObj.addEventListener('mousemove', this.ttt);
    },
    /**
     * 提供给外部调用的方法，可以设置颜色
     * @param colorString 颜色的十六进制写法（含#），支持RGBA
     */
    setColor(colorString) {
      if (!colorString || !(/^#[\dA-F]{3}$/.test(colorString) || /^#[\dA-F]{6}$/.test(colorString) || /^#[\dA-F]{8}$/.test(colorString))) {
        return;
      }
      this.colorResult = colorString;
      this.handleResultInput(colorString);
    },
    /**
     * 提供给外部调用的方法，获取颜色
     */
    getColor() {
      return this.colorResult;
    },
    /**
     * 提供给外部调用的方法，显示调色板
     */
    open() {
      this.showPalatte = true;
    },
    /**
     * 提供给外部调用的方法，隐藏调色板
     */
    close() {
      this.showPalatte = false;
    },
    /* *********************其余不建议(但不会阻止)外部直接调用的方法************************************************ */
    /**
    * 处理字符串类型的色彩，转化为 {R:#,G:#,B:#,A:#}
    * @params: baseColor string 十六进制色彩字符串
    * @returs: {R:#,G:#,B:#,A:#} dict #为对应的十进制数值
    */
    colorStringToRGBA(baseColor) {
      if (typeof baseColor === 'string' && baseColor.charAt(0) === '#') {
        // 形如 #FF0000 的色彩字符串
        baseColor = baseColor.replace('#', '');
        if (baseColor.length !== 3 && baseColor.length !== 6 && baseColor.length !== 8) {
          return null;
        }
        if (baseColor.length === 3) {
          const tmpArr = baseColor.split('');
          baseColor = '';
          for (let i = 0; i < tmpArr.length; i++) {
            baseColor += tmpArr[i] + tmpArr[i];
          }
        }
        const resultColor = {
          R: parseInt(baseColor.slice(0, 2), 16),
          G: parseInt(baseColor.slice(2, 4), 16),
          B: parseInt(baseColor.slice(4, 6), 16)
        };
        if (baseColor.length === 8) {
          resultColor.A = (parseInt(baseColor.slice(6, 8), 16) / 255).toFixed(2) * 1;
        }
        return resultColor;
      }
      if (typeof baseColor === 'string' && baseColor.slice(0, 3).toLowerCase() === 'rgb') {
        // 形如 rgb(r,g,b) / rgba(r,g,b,a)
        const matchArr = baseColor.match(/rgba?\( *(\d+) *, *(\d+) *, *(\d+) *(?:, *(1|0\.\d+) *)?\)/i);
        if (!matchArr) { return null; }
        const resultColor = {
          R: matchArr[1] * 1,
          G: matchArr[2] * 1,
          B: matchArr[3] * 1
        };
        if (matchArr[4] !== undefined) {
          resultColor.A = matchArr[4] * 1;
        }
        return resultColor;
      }
      return baseColor;
    },
    /**
    * 处理{R:#,G:#,B:#}，转化为字符串类型的色彩
    * @params: {R:#,G:#,B:#} dict #为对应的十进制数值
    * @returs: Color string 十六进制色彩字符串
    */
    RGBAToColorString(rgb) {
      if (typeof rgb === 'object' && rgb.R !== undefined) {
        let r,
          g,
          b,
          a = '';
        // 形如 {R:#,G:#,B:#}
        r = (rgb.R).toString(16);
        r < 16 && (r = '0' + r);
        g = rgb.G.toString(16);
        g < 16 && (g = '0' + g);
        b = rgb.B.toString(16);
        b < 16 && (b = '0' + b);
        if (rgb.A !== undefined) {
          a = Math.round(rgb.A * 100).toString(16);
          a < 16 && (a = '0' + a);
        }
        return '#' + r + g + b + a;
      }
      return rgb;
    },
    /**
    * 处理{R:#,G:#,B:#}/colorString，转化为 {H:#,S:#,V:#} 色彩值
    * @params: rgb dict/string
    * @returs: {H:#,S:#,V:#} dict
    */
    RGBToHSV(rgb) {
      let color;
      if (typeof rgb === 'string' && rgb.charAt(0) === '#') {
        color = this.colorStringToRGBA(rgb);
      } else if (typeof rgb === 'object' && rgb.R !== undefined) {
        color = rgb;
      } else {
        return undefined;
      }

      const r = color.R,
        g = color.G,
        b = color.B;
      const max = r > g ? (r > b ? r : b) : (g > b ? g : b),
        min = r < g ? (r < b ? r : b) : (g < b ? g : b);
      let h,
        s;
      // rgb --> hsv(hsb)
      if (max === min) {
        h = 0;    // 定义里面应该是undefined的，不过为了简化运算，还是赋予0算了。
      } else if (max === r) {
        h = 60 * (g - b) / (max - min);
        if (g < b) { h += 360; }
      } else if (max === g) {
        h = 60 * (b - r) / (max - min) + 120;
      } else if (max === b) {
        h = 60 * (r - g) / (max - min) + 240;
      }
      if (max === 0) { s = 0; } else {
        s = (max - min) / max;
      }
      const v = max;
      return {H: h, S: s, V: v};
    },
    /**
    * 兼容各种浏览器的，获取鼠标在容器中的位置
    * @params: ev event windows 事件
    * @returs: {x:#,y:#}
    */
    mousePosition(ev) {
      const palattePos = this.domPosition(this.palatteObj); // 画布的位置（相对于body）
      if (!ev) { ev = window.event; }
      if (ev.touches && ev.touches.length) {
        // 移动端点击
        const touch = ev.touches[0];
        return {
          x: touch.clientX - palattePos.left,
          y: touch.clientY - palattePos.top
        };
      }
      // 鼠标点击
      if (ev.pageX || ev.pageY) {
        return {
          x: ev.pageX - palattePos.left,
          y: ev.pageY - palattePos.top
        };
      }
      return {
        x: ev.clientX + document.documentElement.scrollLeft - document.body.clientLeft - palattePos.left,
        y: ev.clientY + document.documentElement.scrollTop  - document.body.clientTop - palattePos.top
      };
    },
    /**
    * 兼容各种浏览器的，获取dom的坐标
    * @params: dom HTMLDOM
    * @returs: {top:#,left:#}
    */
    domPosition(dom) {
      let t = dom.offsetTop;
      let l = dom.offsetLeft;
      dom = dom.offsetParent;
      while (dom) {
        t += dom.offsetTop;
        l += dom.offsetLeft;
        dom = dom.offsetParent;
      }
      return {top: t, left: l};
    },
    /**
    * 计算两点间的距离
    * @params: pos1 dict {x:#,y:#} 点1的位置
    * @params: pos2 dict {x:#,y:#} 点2的位置
    * @returs: length float
    */
    poiontLength(pos1, pos2) {
      return Math.sqrt((pos2.x - pos1.x) * (pos2.x - pos1.x) + (pos2.y - pos1.y) * (pos2.y - pos1.y));
    },
    /**
    * 产生色环
    * @params: ctx canvas_context 已经初始化后的 canvas context
    * @params: x float 圆心 x 坐标
    * @params: y float 圆心 y 坐标
    * @params: outterRadius float 圆的外径
    * @params: innerRadius float 圆的内径
    * @params: wearProof float 细腻度(>0,越小越细腻)
    * @returs: false
    */
    colorRing(ctx, x, y, outterRadius, innerRadius, wearProof) {
      for (let i = outterRadius; i >= innerRadius; i -= wearProof) {
        let r = 255,
          g = 0,
          b = 0,
          flag = 1;    // rgb 对应红绿蓝三色的数值， flag 指色彩渐变过程序号
        for (let j = 0; j < Math.PI * 2; j += Math.PI / 720) {
          ctx.strokeStyle = 'rgb(' + r + ',' + g + ',' + b + ')';
          ctx.beginPath();
          ctx.arc(x, y, i, j, j + Math.PI / 720, false);
          ctx.stroke();
          // 变化规则
          switch (flag) {
            case 1:
              if (g >= 255) { g = 255; r = 254; flag = 2; break; }
              g++; break;
            case 2:
              if (r <= 0) { r = 0; b = 1; flag = 3; break; }
              r--; break;
            case 3:
              if (b >= 255) { b = 255; g = 254; flag = 4; break; }
              b++; break;
            case 4:
              if (g <= 0) { g = 0; r = 1; flag = 5; break; }
              g--; break;
            case 5:
              if (r >= 255) { r = 255; b = 254; flag = 6; break; }
              r++; break;
            case 6:
              if (b <= 0) { flag = null; break; }
              b--; break;
            default: break;
          }
        }
      }
      return false;
    },
    /**
    * 产生中间方形灰度选择块
    * @params: ctx canvas_context 已经初始化后的 canvas context
    * @params: x float 左上顶点 x 坐标
    * @params: y float 左上顶点 y 坐标
    * @params: w float 色块的宽
    * @params: h float 色块的高
    * @params: baseColor string/dict 定义基准色(右上角的色彩)，接受一个色彩字符串或者含有 R/G/B 元素的字典
    * @returs: false
    */
    colorPalatte(ctx, x, y, w, h, baseColor) {
      // 先清除原有的内容
      ctx.clearRect(x - 1, y - 1, w + 2, h + 2);
      // 画图
      let r,
        g,
        b;
      const unitI = h / 255;
      baseColor = this.colorStringToRGBA(baseColor);    // 处理字符串类型的色彩，转化为 {R:#,G:#,B:#}
      if (!baseColor) { return false; }
      for (let i = 0; i < h; i += unitI) {
        const lg6 = ctx.createLinearGradient(x, y, x + w, y);
        r = g = b = Math.floor(255 - i * 255 / h);    // 左侧边缘色彩
        lg6.addColorStop(0, 'rgb(' + r + ',' + g + ',' + b + ')');
        r = baseColor.R - i * 255 / h;        // 右侧边缘色彩
        g = baseColor.G - i * 255 / h;        // 因为i被等分了，
        b = baseColor.B - i * 255 / h;        // 所以需要反转单位
        // 保证不能小于0，不能大于 255
        r = r < 0 ? 0 : (r > 255 ? 255 : r);
        g = g < 0 ? 0 : (g > 255 ? 255 : g);
        b = b < 0 ? 0 : (b > 255 ? 255 : b);
        r = Math.floor(r); g = Math.floor(g); b = Math.floor(b);    // rgb 函数只接受整数
        lg6.addColorStop(1, 'rgb(' + r + ',' + g + ',' + b + ')');
        ctx.strokeStyle = lg6;
        ctx.beginPath();
        ctx.moveTo(x, y + i);
        ctx.lineTo(x + w, y + i);
        ctx.stroke();
      }
      return false;
    },
    /**
    * 产生透明度滑动条
    * @params: ctx canvas_context 已经初始化后的 canvas context
    * @params: x float 左上顶点 x 坐标
    * @params: y float 左上顶点 y 坐标
    * @params: w float 滑动条的宽
    * @params: h float 滑动条的高
    * @params: baseColor string/dict 定义基准色(右侧的色彩)，接受一个色彩字符串或者含有 R/G/B 元素的字典
    * @returs: false
    */
    colorSlider(ctx, x, y, w, h, baseColor) {
      baseColor = this.colorStringToRGBA(baseColor);    // 处理字符串类型的色彩，转化为 {R:#,G:#,B:#}
      if (!baseColor) { return false; }
      // 先清除原有的内容
      ctx.clearRect(x - 1, y - 1, w + 2, h + 2);
      // 画背景透明方格
      ctx.fillStyle = 'rgba(0,0,0,0.3)';
      const halfH = Math.floor(h / 2),
        gridCnt = Math.floor(w / halfH);
      for (let i = 0; i < gridCnt; i += 2) {
        if ((x + i * halfH) < (x + w)) { ctx.fillRect(x + i * halfH, y, halfH, halfH); }
        if ((x + (i + 1) * halfH) < (x + w)) { ctx.fillRect(x + (i + 1) * halfH, y + halfH, halfH, halfH); }
      }
      // 产生透明条
      const lg6 = ctx.createLinearGradient(x, y, w, y);
      lg6.addColorStop(0, 'rgba(' + baseColor.R + ',' + baseColor.G + ',' + baseColor.B + ',0)');
      lg6.addColorStop(1, 'rgba(' + baseColor.R + ',' + baseColor.G + ',' + baseColor.B + ',1)');
      ctx.fillStyle = lg6;
      ctx.strokeStyle = '#000000';
      ctx.fillRect(x, y, w, h);
      ctx.strokeRect(x, y, w, h);
      return false;
    },
    /**
    * 产生预览
    * @params: ctx canvas_context 已经初始化后的 canvas context
    * @params: x float 左上顶点 x 坐标
    * @params: y float 左上顶点 y 坐标
    * @params: w float 预览的宽
    * @params: h float 预览的高
    * @params: currentColor string/dict 定义当前颜色，接受一个色彩字符串或者含有 R/G/B/A 元素的字典
    * @params: newColor string/dict 定义新选择的颜色，接受一个色彩字符串或者含有 R/G/B/A 元素的字典
    * @returs: false
    */
    colorPreview(ctx, x, y, w, h, currentColor, newColor) {
      // 如果没有传入 newColor ，则认为其实是没有传入 currentColor
      if (!newColor) {
        newColor = currentColor;
        currentColor = undefined;
      }

      // 产生预览（当前颜色）
      if (currentColor) {
        ctx.clearRect(x, y, w / 2, h);
        // 画背景透明方格
        ctx.fillStyle = 'rgba(0,0,0,0.3)';
        ctx.fillRect(x, y, w / 4, h / 2);
        ctx.fillRect(x + w / 4, y + h / 2, w / 4, h / 2);
        currentColor = this.colorStringToRGBA(currentColor);    // 处理字符串类型的色彩
        ctx.fillStyle = 'rgba(' + currentColor.R + ',' + currentColor.G + ',' + currentColor.B + ',' + (currentColor.A ? currentColor.A : 1) + ')';
        ctx.fillRect(x, y, w / 2, h);
      }

      // 产生预览（新颜色）
      if (newColor) {
        ctx.clearRect(x + w / 2, y, w / 2, h);
        // 画背景透明方格
        ctx.fillStyle = 'rgba(0,0,0,0.3)';
        ctx.fillRect(x + w / 2, y, w / 4, h / 2);
        ctx.fillRect(x + w * 3 / 4, y + h / 2, w / 4, h / 2);
        newColor = this.colorStringToRGBA(newColor);    // 处理字符串类型的色彩，转化为 {R:#,G:#,B:#};
        if (!newColor) { return false; }
        ctx.fillStyle = 'rgba(' + newColor.R + ',' + newColor.G + ',' + newColor.B + ',' + (newColor.A !== undefined ? newColor.A : 1) + ')';
        ctx.fillRect(x + w / 2, y, w / 2, h);
      }

      // 边框
      ctx.strokeStyle = '#000000';
      ctx.strokeRect(x, y, w, h);
      return false;
    },
    /**
    * 重置颜色
    * 进行了任意一项设置之后，需要进行颜色重置
    * @params: ctx canvas_context 已经初始化后的 canvas context
    * @params: posDict dict {ringPos:{x,y}, palattePos:{x,y}, sliderPos:{x,y}} 均是相对于画布左上角的绝对坐标
    * @returs: newColor
    */
    resetColor(ctx, posDict) {
      if (!posDict.ringPos || !posDict.palattePos || !posDict.sliderPos) { return false; }

      let newColor,
        x,
        y,
        w,
        h;
      // 计算色相环的值
      x = posDict.ringPos.x; y = posDict.ringPos.y;
      let b = x - this.opt.center.x,
        a = y - this.opt.center.y,
        alpha,
        r,
        g;
      if (b === 0) { alpha = Math.PI / 2; } else { alpha = Math.abs(Math.atan(a / b)); }
      if (a >= 0 && b > 0 && alpha <= Math.PI / 3) {
        r = 255;
        g = alpha * 255 * 3 / Math.PI;
        b = 0;
      } else if (a > 0 && b >= 0 && Math.PI / 3 < alpha) {
        r = 255 * 2 - alpha * 255 * 3 / Math.PI;
        g = 255;
        b = 0;
      } else if (a > 0 && b < 0 && Math.PI / 3 < alpha) {
        r = alpha * 255 * 3 / Math.PI - 255;
        g = 255;
        b = 0;
      } else if (a >= 0 && b < 0 && alpha <= Math.PI / 3) {
        r = 0;
        g = 255;
        b = 255 - alpha * 255 * 3 / Math.PI;
      } else if (a < 0 && b < 0 && alpha <= Math.PI / 3) {
        r = 0;
        g = 255 - alpha * 255 * 3 / Math.PI;
        b = 255;
      } else if (a < 0 && b < 0 && Math.PI / 3 < alpha) {
        r = alpha * 255 * 3 / Math.PI - 255;
        g = 0;
        b = 255;
      } else if (a < 0 && b >= 0 && Math.PI / 3 < alpha) {
        r = 255 * 2 - alpha * 255 * 3 / Math.PI;
        g = 0;
        b = 255;
      } else if (a < 0 && b > 0 && alpha <= Math.PI / 3) {
        r = 255;
        g = 0;
        b = alpha * 255 * 3 / Math.PI;
      }
      r = Math.floor(r); g = Math.floor(g); b = Math.floor(b);
      newColor = {
        R: r,
        G: g,
        B: b
      };
      // 重置方形色块
      this.colorPalatte(ctx, this.opt.palattePos.x, this.opt.palattePos.y, this.opt.palatteSize.w, this.opt.palatteSize.h, newColor);

      // 计算方形色块色值
      x = posDict.palattePos.x - this.opt.palattePos.x;
      y = posDict.palattePos.y - this.opt.palattePos.y;
      w = this.opt.palatteSize.w;
      // eslint-disable-next-line prefer-const
      h = this.opt.palatteSize.h;
      // 计算右侧终点色值(newColor的y偏移)，此处的计算规则请参考 _colorPalatte
      r = newColor.R - y * 255 / h;
      g = newColor.G - y * 255 / h;
      b = newColor.B - y * 255 / h;
      r = r < 0 ? 0 : r > 255 ? 255 : r; g = g < 0 ? 0 : g > 255 ? 255 : g; b = b < 0 ? 0 : b > 255 ? 255 : b;
      r = Math.floor(r); g = Math.floor(g); b = Math.floor(b);
      // 计算 x 偏移色值
      const l = Math.floor(255 - y * 255 / h);    // 这个是左侧的起点色值(白色的y偏移)
      r = l - (l - r) * x / w;
      g = l - (l - g) * x / w;
      b = l - (l - b) * x / w;
      r = Math.floor(r); g = Math.floor(g); b = Math.floor(b);
      newColor = {R: r, G: g, B: b};

      // 计算透明度
      x = posDict.sliderPos.x - this.opt.sliderPos.x;
      w = this.opt.sliderSize.w - this.opt.sliderSelectorSize.w / 2 - 1;
      a = (x / w).toFixed(2);
      a >= 0.99 && (a = 1);
      a <= 0.01 && (a = 0);
      if (a < 1) { newColor.A = a * 1; }

      // 生成预览颜色
      this.colorPreview(ctx, this.opt.previewPos.x, this.opt.previewPos.y, this.opt.previewSize.w, this.opt.previewSize.h, newColor);
      // 生成透明度滑动条
      this.colorSlider(ctx, this.opt.sliderPos.x, this.opt.sliderPos.y, this.opt.sliderSize.w, this.opt.sliderSize.h, newColor);
      return newColor;
    },
    /**
    * 重置所有选择器
    * @params: ringObj object ringSelector 对象
    * @params: palatteObj object ringSelector 对象
    * @params: sliderObj object ringSelector 对象
    * @params: color dict/string
    * @returs: false
    */
    resetSelector(ctx, ringObj, palatteObj, sliderObj, color) {
      // 转换成 HSV 色值
      const HSV = this.RGBToHSV(color),
        posDict = this.positionDict;
      let x,
        y;
      // 设置色相环选点位置
      const halfR = (this.opt.radius.inner + this.opt.radius.outter) / 2;
      let halfS = this.opt.ringSelectorSize / 2 + 1;
      x = this.opt.center.x + halfR * Math.cos(HSV.H * Math.PI / 180);
      y = this.opt.center.y + halfR * Math.sin(HSV.H * Math.PI / 180);
      posDict.ringPos = {x: x, y: y};
      x -= halfS; y -= halfS;
      ringObj.style.left = x + 'px';
      ringObj.style.top = y + 'px';
      // 设置饱和度/明度位置
      halfS = this.opt.palatteSelectorSize / 2 + 1;
      x = this.opt.palattePos.x + HSV.S * this.opt.palatteSize.w;
      y = this.opt.palattePos.y + (1 - HSV.V / 255) * this.opt.palatteSize.h;
      posDict.palattePos = {x: x, y: y};
      x -= halfS; y -= halfS;
      palatteObj.style.left = x + 'px';
      palatteObj.style.top = y + 'px';
      if (color.A !== undefined && color.A < 0.99) {
        x = this.opt.sliderPos.x + color.A * (this.opt.sliderSize.w - this.opt.sliderSelectorSize.w / 2 - 1);
        posDict.sliderPos = {x: x, y: 0};
        sliderObj.style.left = x + 'px';
      }
      this.resetColor(ctx, posDict);
      return false;
    },
    /**
    * 写入结果文本框
    * @params: color dict/string 颜色字符串
    * @returs: false
    */
    writeColor(color) {
      const colorString = this.RGBAToColorString(color).toUpperCase();
      this.gobalOpts.color = colorString;    // 书写全局变量
      this.colorResult = colorString;
      this.$emit('color', this.colorResult);
      return false;
    },
    /**
    * 移动 ringSelector
    * @params: ctx canvas_context 已经初始化后的 canvas context
    * @params: Obj object ringSelector 对象
    * @params: pos {x:#,y:#} 新的坐标
    * @returs: false
    */
    moveRing(ctx, Obj, pos) {
      const centerLength = this.poiontLength(pos, this.opt.center);    // 鼠标位置距离圆心的距离
      // 需要计算距离最近的色环上的点的坐标
      // 采用平行线等比定理进行运算，同时需要减去 ringSelector 的 half size 和 1px border
      pos = {
        x: (pos.x - this.opt.center.x) * ((this.opt.radius.inner + this.opt.radius.outter) / 2) / centerLength + this.opt.center.x - this.opt.ringSelectorSize / 2 - 1,
        y: (pos.y - this.opt.center.y) * ((this.opt.radius.inner + this.opt.radius.outter) / 2) / centerLength + this.opt.center.y - this.opt.ringSelectorSize / 2 - 1
      };
      Obj.style.left = pos.x + 'px';
      Obj.style.top = pos.y + 'px';

      this.positionDict.ringPos = pos;
      const newColor = this.resetColor(ctx, this.positionDict);
      this.writeColor(newColor);
      return false;
    },
    /**
    * 移动 palatteSelector
    * @params: ctx canvas_context 已经初始化后的 canvas context
    * @params: Obj object palatteSelector 对象
    * @params: pos {x:#,y:#} 新的坐标
    * @returs: false
    */
    movePalatte(ctx, Obj, pos) {
      // 需要计算距离最近的色块上的点的坐标
      pos = {
        x: pos.x < this.opt.palattePos.x ? this.opt.palattePos.x : pos.x > this.opt.palattePos.x + this.opt.palatteSize.w ? this.opt.palattePos.x + this.opt.palatteSize.w : pos.x,
        y: pos.y < this.opt.palattePos.y ? this.opt.palattePos.y : pos.y > this.opt.palattePos.y + this.opt.palatteSize.h ? this.opt.palattePos.y + this.opt.palatteSize.h : pos.y
      };

      // 未减去 palatteSelector 的 half size 前，先进行色彩重置
      this.positionDict.palattePos = {
        x: pos.x,
        y: pos.y
      };
      const newColor = this.resetColor(ctx, this.positionDict);
      this.writeColor(newColor);

      // 同时需要减去 palatteSelector 的 half size 和 1px border
      pos = {
        x: pos.x - this.opt.palatteSelectorSize / 2 - 1,
        y: pos.y - this.opt.palatteSelectorSize / 2 - 1
      };
      Obj.style.left = pos.x + 'px';
      Obj.style.top = pos.y + 'px';
      return false;
    },
    /**
    * 移动 sliderSelector
    * @params: ctx canvas_context 已经初始化后的 canvas context
    * @params: Obj object sliderSelector 对象
    * @params: pos {x:#,y:#} 新的坐标
    * @returs: false
    */
    moveSlider(ctx, Obj, pos) {
      // 先减去 sliderSelector 的 half width 和 1px border
      pos = {
        x: pos.x - this.opt.sliderSelectorSize.w / 2 - 1,
        y: this.opt.sliderPos.y
      };
      // 然后计算距离最近的滑动条上的点的 x 坐标
      pos.x = pos.x < this.opt.sliderPos.x ? this.opt.sliderPos.x : pos.x > this.opt.sliderPos.x + this.opt.sliderSize.w - this.opt.sliderSelectorSize.w ? this.opt.sliderPos.x + this.opt.sliderSize.w - this.opt.sliderSelectorSize.w - 1 : pos.x - this.opt.sliderSelectorSize.w / 2;

      Obj.style.left = pos.x + 'px';
      Obj.style.top = pos.y + 'px';

      this.positionDict.sliderPos = {
        x: pos.x,
        y: 0
      };
      const newColor = this.resetColor(ctx, this.positionDict);
      this.writeColor(newColor);
      return false;
    },
    /**
     * 绑定色环按下事件
     */
    handlePalatteMouseDown(e) {
      const mousePos = this.mousePosition(e);     // 鼠标位置
      if (mousePos.x > 0 && mousePos.x < this.opt.radius.outter * 2
                    && mousePos.y > 0 && mousePos.y < this.opt.radius.outter * 2) {
        // 点击色环/色块部分
        const centerLength = this.poiontLength(mousePos, this.opt.center);    // 点击的点距离圆心的距离
        if (this.opt.radius.inner <= centerLength && centerLength <= this.opt.radius.outter) {
          // 如果处于色环带，则激活 ringSelector 的移动策略
          this.moveRing(this.palatteCTX, this.ringSelectorObj, mousePos);
          this.ringDownFlag = true;
          this.bindGolbalEvent();
        } else if (this.opt.palattePos.x < mousePos.x && mousePos.x < this.opt.palattePos.x + this.opt.palatteSize.w
                        && this.opt.palattePos.y < mousePos.y && mousePos.y < this.opt.palattePos.y + this.opt.palatteSize.h) {
          // 点击色块部分，则激活 palatteSelector 的移动策略
          this.movePalatte(this.palatteCTX, this.palatteSelectorObj, mousePos);
          this.palatteDownFlag = true;
          this.bindGolbalEvent();
        }
      } else if (this.opt.sliderPos.x < mousePos.x && mousePos.x < this.opt.sliderPos.x + this.opt.sliderSize.w
                    && this.opt.sliderPos.y < mousePos.y && mousePos.y < this.opt.sliderPos.y + this.opt.sliderSize.h) {
        // 透明度滑动条部分
        this.moveSlider(this.palatteCTX, this.sliderSelectorObj, mousePos);
        this.sliderDownFlag = true;
        this.bindGolbalEvent();
      }
    },
    /**
     * 绑定输入框数据变化事件
     * 加入防抖
     */
    handleResultInput: debounce(inputColor => {
      // 如果传入了颜色，则优先以传入的颜色为准
      const val = inputColor || That.colorResult;
      let color;
      // 兼容rgb的输入模式
      if (val.charAt(0) === '#' && (val.length === 4 || val.length === 7 || val.length === 9)) {
        color = That.colorStringToRGBA(val);
      } else if (val.length > 3 && val.slice(0, 3).toLowerCase() === 'rgb' && val.charAt(val.length - 1) === ')') {
        color = That.colorStringToRGBA(val);
        if (!color) { return; }
      } else {
        return;
      }
      That.resetSelector(That.palatteCTX, That.ringSelectorObj, That.palatteSelectorObj, That.sliderSelectorObj, color);
    }, 200),
    handleConfirm() {
      // this.$emit('color', this.colorResult);
      this.close();
    },
    /**
    * 绑定body事件
    */
    bindGolbalEvent() {
      // vue 必须使用绑定在this上的函数，才能解绑
      window.addEventListener('mousemove', this.contanerMousemove);
      window.addEventListener('touchmove', this.contanerMousemove);
      window.addEventListener('mouseup', this.contanerMouseup);
      window.addEventListener('touchend', this.contanerMouseup);
    },
    /**
    * 解绑body事件
    */
    unbindGolbalEvent() {
      window.removeEventListener('mousemove', this.contanerMousemove);
      window.removeEventListener('touchmove', this.contanerMousemove);
      window.removeEventListener('mouseup', this.contanerMouseup);
      window.removeEventListener('touchend', this.contanerMouseup);
    },
    /**
     * ************************* 下面是各种鼠标事件的实现 *************************
     */
    contanerMousemove(e) {
      if (this.ringDownFlag || this.palatteDownFlag || this.sliderDownFlag) {
        const mousePos = this.mousePosition(e);
        if (this.ringDownFlag) {
          // 色环选中ing
          this.moveRing(this.palatteCTX, this.ringSelectorObj, mousePos);
        } else if (this.palatteDownFlag) {
          // 色块选中ing
          this.movePalatte(this.palatteCTX, this.palatteSelectorObj, mousePos);
        } else if (this.sliderDownFlag) {
          // 滑动条选中ing
          this.moveSlider(this.palatteCTX, this.sliderSelectorObj, mousePos);
        }
      }
    },
    contanerMouseup(e) {
      this.ringDownFlag = false;
      this.palatteDownFlag = false;
      this.sliderDownFlag = false;
      this.unbindGolbalEvent();
    },
    ringMousedown(e) {
      const mousePos = this.mousePosition(e);
      this.moveRing(this.palatteCTX, this.ringSelectorObj, mousePos);
      this.ringDownFlag = true;
      this.bindGolbalEvent();
    },
    palatteMousedown(e) {
      const mousePos = this.mousePosition(e);
      this.moveRing(this.palatteCTX, this.palatteSelectorObj, mousePos);
      this.palatteDownFlag = true;
      this.bindGolbalEvent();
    },
    sliderMousedown(e) {
      const mousePos = this.mousePosition(e);
      this.moveRing(this.palatteCTX, this.sliderSelectorObj, mousePos);
      this.sliderDownFlag = true;
      this.bindGolbalEvent();
    }
  }
};
</script>

<style scoped>
/* 为了避免 sass 在国内带来的麻烦，这里将所有的class打平了 */
.colorPalatte {
  background: #f9f9f9;
  width: 205px;
  height: 280px;
  margin-top: 5px;
  border-radius: 5px;
  border: 2px solid rgb(51, 51, 51);
  position: relative;
  cursor: crosshair;
  user-select: none;
}
.colorPalatte .ringSelector {
  border: 1px solid rgb(0, 0, 0);
  background: rgba(255, 255, 255, 0.7);
  width: 8px;
  height: 8px;
  border-radius: 8px;
  position: absolute;
  z-index: 10;
  left: 35.8635px;
  top: 24.5239px;
}
.colorPalatte .plaltteSelector {
  border: 1px solid rgb(0, 0, 0);
  background: rgba(255, 255, 255, 0.7);
  width: 4px;
  height: 4px;
  border-radius: 4px;
  position: absolute;
  z-index: 10;
  left: 147px;
  top: 67px;
}
.colorPalatte .sliderSelector {
  border: 1px solid #000;
  background: rgba(255,255,255,0.7);
  width: 4px;
  height: 19px;
  position: absolute;
  z-index: 10;
  left: 195px;
  top: 210px;
}
.colorPalatte .cancas {
  position: absolute;
  z-index: 5;
  left: 0;
  top: 0;
}
.colorPalatte .result {
  line-height: 1.42857143;
  padding: 6px;
  color: #555;
  background: #FFF;
  border: 1px solid #CCC;
  width: 140px;
  position: absolute;
  z-index: 5;
  left: 0;
  top: 240px;
}
.colorPalatte .confirmBtn {
  line-height: 1.42857143;
  padding: 6px;
  color: #FFF;
  background: #337AB7;
  border: 1px solid #CCC;
  position: absolute;
  z-index: 5;
  left: 160px;
  top: 240px;
  border-radius: 5px;
  cursor: pointer;
}
.colorPalatte .closeBtn {
  color: #777;
  background: transparent;
  position: absolute;
  z-index: 5;
  right: 2px;
  top: 2px;
  cursor: pointer;
}
</style>